package com.evil.kingdom.utils.tactics;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.evil.common.core.exception.BusinessException;
import com.evil.kingdom.pojo.dto.person.base.PersonDto;
import com.evil.kingdom.pojo.dto.person.soldier.SoldierDto;
import com.evil.kingdom.pojo.entity.action.AbstractPosition;
import com.evil.kingdom.pojo.entity.action.BattleLayout;
import com.evil.kingdom.pojo.entity.action.BattlePerson;
import com.evil.kingdom.pojo.entity.base.Fight;
import com.evil.kingdom.pojo.entity.tactics.Tactics;
import com.evil.kingdom.pojo.enums.PhalanxLayoutEnum;
import com.evil.kingdom.pojo.enums.result.RCodeEnum;
import com.evil.kingdom.utils.DijkstraUtil;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 战术工具
 *
 * @author liyang
 * @date 2024-07-03 10:18
 */
@Slf4j
public class TacticsUtil {

    public static <T extends PersonDto> BattleLayout layout(Tactics tactics, Collection<T> persons, Fight addition, boolean side) {
        BattleLayout layout = new BattleLayout();
        if (CollectionUtil.isEmpty(persons)) {
            return layout;
        }
        // 非战术站位目前最大值
        AbstractPosition max = new AbstractPosition(0d, 0d);
        if (null == tactics) {
            // 无战术
            for (T person : persons) {
                // 非战术站位
                while (layout.containsValue(side(max, side))) {
                    max.setX(randomX());
                    max.setY(randomY());
                }
                layout.put(new BattlePerson<>(person, addition), side(max, side));
            }
            return layout;
        }

        Tactics.Phalanxs phalanxs = tactics.getPhalanxs();
        averageSplit(persons, phalanxs).forEach((phalanx, list) -> {
            List<AbstractPosition> positions = generatePositionsInPhalanx(phalanx, list.size());
            if (positions.size() != list.size()) {
                throw new BusinessException(RCodeEnum.RUN_ERROR, "战术布局失败");
            }
            for (int i = 0; i < list.size(); i++) {
                layout.put(new BattlePerson<>(list.get(i), addition), positions.get(i));
            }
        });

        return layout;
    }

    /**
     * 根据方阵表平均分隔人物
     *
     * @param persons  persons列表
     * @param phalanxs 方阵表
     * @param <T>      T
     * @return map
     */
    public static <T extends PersonDto> Map<Tactics.Phalanx, List<T>> averageSplit(Collection<T> persons, Tactics.Phalanxs phalanxs) {
        Map<Tactics.Phalanx, List<T>> result = new LinkedHashMap<>();
        ArrayList<PhalanxLayoutEnum> keys = new ArrayList<>(phalanxs.keySet());
        AtomicInteger index = new AtomicInteger();
        persons.forEach(person -> {
            Tactics.Phalanx key = phalanxs.get(keys.get(index.get()));
            if (!result.containsKey(key)) {
                result.put(key, new ArrayList<>());
            }
            result.get(key).add(person);

            index.getAndIncrement();
            if (index.get() >= keys.size()) {
                index.set(0);
            }
        });
        return result;
    }

    /**
     * 根据方阵端点表生成指定个数的点
     *
     * @param phalanx phalanx
     * @param count   count
     * @return 列表
     */
    public static List<AbstractPosition> generatePositionsInPhalanx(Tactics.Phalanx phalanx, int count) {
        // 计算多边形边界矩形
        double minX = Double.POSITIVE_INFINITY;
        double minY = Double.POSITIVE_INFINITY;
        double maxX = Double.NEGATIVE_INFINITY;
        double maxY = Double.NEGATIVE_INFINITY;
        for (AbstractPosition p : phalanx) {
            minX = Math.min(minX, p.getX());
            minY = Math.min(minY, p.getY());
            maxX = Math.max(maxX, p.getX());
            maxY = Math.max(maxY, p.getY());
        }
        final double rectWidth = maxX - minX;
        final double rectHeight = maxY - minY;

        // 计算方阵端点表面积
        double phalanxArea = calPhalanxArea(phalanx);
        double s = rectWidth * rectHeight;
        double actCount = count / (phalanxArea / s);

        // 比例
        boolean b = rectWidth > rectHeight;
        double max = b ? rectWidth / rectHeight : rectHeight / rectWidth;
        Optional<Factor> optional = findFactors(actCount).stream().min(Comparator.comparingDouble(e -> Math.abs(e.proportion() - max)));
        if (optional.isEmpty()) {
            return new ArrayList<>();
        }
        Factor min = optional.get();
        double stepWidth = floor(rectWidth / (b ? min.a.doubleValue() : min.b.doubleValue()), 2);
        double stepHeight = floor(rectHeight / (b ? min.b.doubleValue() : min.a.doubleValue()), 2);

        List<AbstractPosition> positions = new ArrayList<>();
        double x = minX;
        double y = minY;
        while (true) {
            AbstractPosition position = new AbstractPosition(x, y);
            if (isInsidePhalanx(position, phalanx)) {
                positions.add(position);
            }
            if (positions.size() >= count) {
                break;
            }
            x += stepWidth;
            if (x > maxX) {
                x = minX;
                y += stepHeight;
                if (y > maxY) {
                    break;
                }
            }
        }

        return positions;
    }

    /**
     * 判断是否在方阵端点表内
     * 可以使用“射线法”来判断一个点是否在多边形内。
     * 该方法的基本原理是发射一条从该点向无穷远处的光线，
     * 然后计算该光线与多边形边的交点数量。
     * 如果交点的数量是奇数，则点在多边形内；
     * 如果是偶数，则在多边形外。
     *
     * @param position 坐标
     * @param phalanx  方阵端点列表
     * @return boolean
     */
    private static boolean isInsidePhalanx(AbstractPosition position, Tactics.Phalanx phalanx) {
        int count = 0;
        int n = phalanx.size();
        AbstractPosition p1, p2;
        for (int i = 0; i < n; i++) {
            p1 = phalanx.get(i);
            p2 = phalanx.get((i + 1) % n);
            if (isOnLine(position, p1, p2)) {
                return true; // 如果点在边界上返回true
            }
            if (isGtLte(position.getY(), p1.getY(), p2.getY())) {
                if (position.getX() <= Math.max(p1.getX(), p2.getX())) {
                    if (!p1.getY().equals(p2.getY())) {
                        double xinters = (position.getY() - p1.getY()) * (p2.getX() - p1.getX()) / (p2.getY() - p1.getY()) + p1.getX();
                        if (p1.getX().equals(p2.getX()) || position.getX() <= xinters) {
                            count++;
                        }
                    }
                }
            }
        }
        return count % 2 == 1;  // 如果点的数量是奇数，说明在内部
    }

    /**
     * 判断点是否在直线上
     *
     * @param point point
     * @param p1    p1
     * @param p2    p2
     * @return boolean
     */
    private static boolean isOnLine(AbstractPosition point, AbstractPosition p1, AbstractPosition p2) {
        DijkstraUtil.Slope p = DijkstraUtil.calSlope(p1, p2);
        DijkstraUtil.Slope pp2 = DijkstraUtil.calSlope(point, p2);
        return p.sameSlope(pp2) && isIn(point.getX(), p1.getX(), p2.getX()) && isIn(point.getY(), p1.getY(), p2.getY());
    }

    /**
     * 计算方阵端点表（多边形）面积
     *
     * @param phalanx phalanx
     * @return double
     */
    public static double calPhalanxArea(Tactics.Phalanx phalanx) {
        int n = phalanx.size();
        if (n < 3) return 0;
        double area = 0;
        for (int i = 0; i < n; i++) {
            area += phalanx.get(i).getX() * phalanx.get((i + 1) % n).getY() - phalanx.get(i).getY() * phalanx.get((i + 1) % n).getX();
        }
        return Math.abs(area / 2);
    }

    public static double floor(Double d, int scale) {
        return new BigDecimal(d).setScale(scale, RoundingMode.FLOOR).doubleValue();
    }

    public static boolean isIn(Double num, Double a, Double b) {
        return num >= Math.min(a, b) && num <= Math.max(a, b);
    }

    public static boolean isGtLte(Double num, Double a, Double b) {
        return num > Math.min(a, b) && num <= Math.max(a, b);
    }

    public static double randomX() {
        return RandomUtil.randomDouble(0, 50, 2, RoundingMode.HALF_DOWN);
    }

    public static double randomY() {
        return RandomUtil.randomDouble(0, 100, 2, RoundingMode.HALF_DOWN);
    }

    public static AbstractPosition side(AbstractPosition position, boolean side) {
        return new AbstractPosition(side ? position.getX() : 100 - position.getX(), position.getY());
    }

    /**
     * 寻找一个数的因子。
     * 通过将给定的数字除以一系列可能的除数，找出所有整除的除数对。
     * 该方法针对小数点后一位的除数进行遍历，以寻找因子。
     *
     * @param number 输入的数字，寻找它的因子。
     * @return 返回一个包含所有因子的列表。每个因子都以Factor对象的形式表示，包含两个因子：被除数和除数。
     */
    public static List<Factor> findFactors(double number) {
        // 初始化一个列表来存储找到的因子。
        List<Factor> factors = new ArrayList<>();
        // 将输入的double类型数字转换为BigDecimal类型，以进行更精确的计算。
        BigDecimal n = BigDecimal.valueOf(number);
        // 计算number的平方根，并转换为BigDecimal类型，以确定遍历的上限。
        BigDecimal max = BigDecimal.valueOf(Math.sqrt(number));
        // 从1开始，以0.1的步长遍历到number的平方根，寻找因子。
        for (BigDecimal i = BigDecimal.ONE; i.compareTo(max) <= 0; i = i.add(new BigDecimal("0.1"))) {
            // 如果number除以i的余数为0，说明i是number的因子。
            if (n.divideAndRemainder(i)[1].compareTo(BigDecimal.ZERO) == 0) {
                // 计算另一个因子，即number除以i的结果。
                BigDecimal r = n.divide(i, 2, RoundingMode.HALF_DOWN);
                // 根据i和r的大小关系，添加Factor对象到列表中。
                factors.add(i.compareTo(r) != 0 ? new Factor(i, r) : new Factor(i, i));
            }
        }
        // 返回包含所有因子的列表。
        return factors;
    }

    private record Factor(BigDecimal a, BigDecimal b) {

        private boolean sign() {
            return a.compareTo(b) > 0;
        }

        public double proportion() {
            return (this.sign() ? a.divide(b, 2, RoundingMode.HALF_DOWN) : b.divide(a, 2, RoundingMode.HALF_DOWN)).doubleValue();
        }
    }

    public static void main(String[] args) {
        boolean onLine = isOnLine(new AbstractPosition(20d, 20d), new AbstractPosition(0d, 0d), new AbstractPosition(20d, 0d));
        log.info("onLine:" + onLine);
        Tactics tactics = new Tactics();
        tactics.setName("TEST");
        tactics.setPhalanxs(new Tactics.Phalanxs() {{
            put(PhalanxLayoutEnum.FRONT, new Tactics.Phalanx(
                    new AbstractPosition(0d, 0d),
                    new AbstractPosition(20d, 0d),
                    new AbstractPosition(0d, 10d)
            ));
        }});

        double polygonArea = calPhalanxArea(tactics.getPhalanxs().get(PhalanxLayoutEnum.FRONT));
        log.info("polygonArea:{}", polygonArea);

        ArrayList<SoldierDto> list = ListUtil.toList(
                new SoldierDto("刘1"),
                new SoldierDto("刘2"),
                new SoldierDto("刘3"),
                new SoldierDto("刘4"),
                new SoldierDto("刘5"),
                new SoldierDto("刘6"),
                new SoldierDto("刘7"),
                new SoldierDto("刘8"),
                new SoldierDto("刘9"),
                new SoldierDto("刘10"),
                new SoldierDto("刘1"),
                new SoldierDto("刘2"),
                new SoldierDto("刘3"),
                new SoldierDto("刘4"),
                new SoldierDto("刘5"),
                new SoldierDto("刘6"),
                new SoldierDto("刘7"),
                new SoldierDto("刘8"),
                new SoldierDto("刘9"),
                new SoldierDto("刘10"),
                new SoldierDto("刘1"),
                new SoldierDto("刘2"),
                new SoldierDto("刘3"),
                new SoldierDto("刘4"),
                new SoldierDto("刘5"),
                new SoldierDto("刘6"),
                new SoldierDto("刘7"),
                new SoldierDto("刘8"),
                new SoldierDto("刘9"),
                new SoldierDto("刘10")
        );

        BattleLayout layout = layout(tactics, list, Fight.none(), true);
        log.info("layout:{}", JSON.toJSONString(layout));
    }
}
